package com.wgo.bpot.persist.db4object;


import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.Configuration;
import com.db4o.query.Query;
import com.wgo.bpot.common.transport.exception.RematoException;
import com.wgo.bpot.common.transport.util.ReflectionHelper;
import com.wgo.bpot.domain.common.CommandHistory;
import com.wgo.bpot.domain.common.impl.CommandHistoryImpl;
import com.wgo.bpot.server.persist.BasicPersistService;
import com.wgo.bpot.wiring.db4o.Db4oDomainModelConvention;


public class Db4ObjectPersistService<S> extends BasicPersistService<S> {

	private static final transient Logger log = Logger.getLogger(Db4ObjectPersistService.class.getName());

	private static final String dbFileName = "survey.db";
	private static ObjectContainer db = openDb() ;
	private boolean isTransactionActive = false;
	private volatile static UniqueConceptidentifier uniqueValueIdentifier = null ;
	
	private Map<Serializable, Object> identifierTotransactionObjects = new Hashtable<Serializable, Object> () ;

	private static ObjectContainer openDb () {
		db = Db4o.openFile(dbFileName);
		Configuration conf = db.ext().configure();
		// Db4o.ext().configure() ; //.activationDepth(6);
		return db;
	}
	
	
	private synchronized Long getNextIdentifier() {
		if (null == uniqueValueIdentifier) {
			uniqueValueIdentifier = (UniqueConceptidentifier)db.get(UniqueConceptidentifier.class).get(0);
			db.set ( uniqueValueIdentifier ) ;
		}
		return uniqueValueIdentifier.getNextAvailableIdentifier();
	}


	public synchronized void createSchema() 
	{
		new File ( dbFileName ).delete() ;

		db = openDb() ;
		
		//Db4o.configure().objectClass(
		//   "com.wgo.carModel.common.domain.impl.StreamerSectionImpl").cascadeOnUpdate(true);

		List<Class> implClasses = new ArrayList<Class>(2);
		for (Class<? extends S> rc : getDomainModelConvention().getRootConceptClasses()) {
			Class implClass = getDomainModelConvention().apiToImpl(rc) ; //rc.getPackage().getName() + ".impl." + rc.getSimpleName() + "Impl";
			implClasses.add(implClass);
			for (Object rootConcept : db.get(implClass)) {
				db.delete(rootConcept);
			}
		}

		for (Object uniqueIdentifier : db.get(UniqueConceptidentifier.class) ) {
			db.delete(uniqueIdentifier);
		}

		// delete all manual to be sure..
		for ( Object result : db.get(new Object()) ) {
			db.delete(result);
		}

		db.commit();
		
		UniqueConceptidentifier uniqueValue = null;
		try {
			uniqueValue = UniqueConceptidentifier.class.getConstructor(Long.class).newInstance((Long)Long.parseLong("0"));
		} catch (Exception e) {
			throw new RematoException ( "Unable to initialize unique identifier holder concept.", e ) ;
		}
		uniqueValueIdentifier = uniqueValue ; 
		db.set(uniqueValueIdentifier);

		for (Class implClass : implClasses) {
			Object rootConcept = null;
			try {
				rootConcept = implClass.newInstance();
			} catch (Exception e) {
				throw new RematoException ( "Unable to initialize root concept.", e ) ;
			}
			Long identifier = getNextIdentifier() ;
			ReflectionHelper.invokeMethod(rootConcept, getDomainModelConvention().getSetIdentifierMethod(rootConcept), identifier ) ;
//			identifierTotransactionObjects.put(identifier , rootConcept);
			identifierTotransactionObjects.put(identifier , rootConcept);
			db.set(rootConcept);
		}
	}

	public <T extends S> void save(T instanceToBeSaved) 
	{
		Long identifier = ReflectionHelper.invokeMethod(instanceToBeSaved, getDomainModelConvention().getIdentifierMethod(instanceToBeSaved));
		if (null == identifier) { 
			identifier = getNextIdentifier() ; 
			ReflectionHelper.invokeMethod(instanceToBeSaved, getDomainModelConvention().getSetIdentifierMethod(instanceToBeSaved) , identifier ) ;
		}
		identifierTotransactionObjects.put(identifier , instanceToBeSaved);
//		db.set(instanceToBeSaved);
	}

	public void saveOrUpdate(Object instanceToBeSaved) 
	{
		Long identifier = ReflectionHelper.invokeMethod(instanceToBeSaved, getDomainModelConvention().getIdentifierMethod(instanceToBeSaved));
		if (null == identifier) { 
			identifier = getNextIdentifier() ; 
			ReflectionHelper.invokeMethod(instanceToBeSaved, getDomainModelConvention().getSetIdentifierMethod(instanceToBeSaved) , identifier ) ;
		}
		identifierTotransactionObjects.put(identifier , instanceToBeSaved);
//		db.set(instanceToBeSaved);
	}

	@SuppressWarnings("unchecked")
	public <T extends S> T getInstance(Class<T> type, Serializable id, int linkDepth) 
	{
		T returnInstance = null ;
		try {
			returnInstance = (T) identifierTotransactionObjects.get( id ) ;
			//db.set(returnInstance); // hack due to some strange things.. - seems like erferences to objects are nulled when serialized.. (?)
		} catch (Throwable e) {} //ignore
		if (null == returnInstance) { 
			Query query = db.query();
			query.constrain(type);
			query.descend("dbId").constrain(new Long(id.toString()));
			ObjectSet set = query.execute();
			if( set.size() > 0) {
				returnInstance = (T)set.get(0);
//				db.activate(returnInstance, linkDepth);
			}
		}
		return returnInstance;		
	}

	public void startTransaction() 
	{
        isTransactionActive = true;
//		if ( ! identifierTotransactionObjects.isEmpty() ) { throw new DatabaseException ("A transaction is already active. Db4oPersistentLayer does currently not support hirearichal transactions.") ; }
//		db.commit();  //  <-- This was old code. Does not seem to make sense...?
		//HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
	}

	public List find(Class type, int linkDept) 
	{
		//Criteria crit = HibernateUtil.getSessionFactory().getCurrentSession().createCriteria(type);
		//return crit.list();
//		log.info(" ***** Persist service Class type: " + type.getName());
		return db.get( type);
	}

	public Object find(Class type, String fieldName, String fieldValue, int linkDepth)
	{
		Class fieldType = null;
		Object fieldObj = null;

		String methodName = null;

		methodName = "get"+fieldName.substring(0,1).toUpperCase() + fieldName.substring(1,fieldName.length());
		Method m0 = null;
		try 
		{
			m0 = type.getMethod(methodName);
			fieldType = m0.getReturnType();
		} 
		catch (Exception e1) 
		{
			e1.printStackTrace();
		}

		try {
			fieldObj = fieldType.getConstructor(String.class).newInstance(fieldValue);
		} catch (Exception e) {
			throw new RematoException ("could not instantiate query object based on fields" , e ) ; 
		} 

		Query query = db.query();
		query.constrain(type);		
		query.descend(fieldName).constrain(fieldObj);

		ObjectSet set = query.execute();
		//System.out.println("length of set = " + set.size())
		if( set.size() > 0) {
			Object returnObject = set.get(0) ;
//			db.activate(returnObject, linkDepth) ; 
			return returnObject ;
		}
		return null; 
	}

	public void close() {

	}

	public boolean isTransactionActive()
	{		
		return isTransactionActive || !identifierTotransactionObjects.isEmpty() ;
	}


	/**
	 * Commit any pending transactions for this session.
	 */
	public void commitTransaction() 
	{
		db.set(uniqueValueIdentifier);
		for (Object touchedInstance : identifierTotransactionObjects.values() ) {
			db.set(touchedInstance) ; // equal to save..
//			log.info("saved: " + touchedInstance.getClass().getSimpleName());
		}
		db.commit();
        identifierTotransactionObjects.clear();
        isTransactionActive = false;
	}


	public void rollbackTransaction() 
	{
		db.rollback();
        identifierTotransactionObjects.clear();   
        isTransactionActive = false;
		log.info("transaction was rolled back");
	}

	public CommandHistory createCommandHistoryEntry() {
		return new CommandHistoryImpl();
	}

	public List<CommandHistory> retrieveCommandHistory(Class conceptType, Long dbId) {
//		Criteria crit = HibernateUtil.getSessionFactory().getCurrentSession().createCriteria(CommandHistoryEjb.class);
//		if (null != conceptType) {
//		crit.add(Restrictions.ilike("conceptClass",conceptType.getName()));
//		}
//		if (null != dbId) {
//		crit.add(Restrictions.eq("conceptDbId",dbId));
//		}
//		List<CommandHistory> result = (List<CommandHistory>)crit.list();
//		Collections.sort(result);

		Query query = db.query();
		query.constrain(conceptType);
		query.descend("dbId").constrain(dbId);
		ObjectSet set = query.execute();
		Collections.sort(set);
		return set;
	}

	public Object openSession() {
		// TODO Auto-generated method stub
		return null;
	}

	public void closeSession() {
//		db.commit();
	}



	public <T extends S> T getDbInstance(Class<T> type, Serializable id) 
	{
		T returnInstance = null ;
		try {
			returnInstance = (T) identifierTotransactionObjects.get( id ) ;
		} catch (Throwable e) {} //ignore
		if (null == returnInstance) { 
			Query query = db.query();
			query.constrain(type);
			query.descend("dbId").constrain(new Long(id.toString()));
			ObjectSet set = query.execute();
			if( set.size() > 0) {
				returnInstance = (T)set.get(0);
				identifierTotransactionObjects.put( id , returnInstance) ;
			}
		}
		return returnInstance ;
	}

	public Object newInstance(Class apiType) throws InstantiationException, IllegalAccessException 
	{
		Object inst = null;
		Class cl = getDomainModelConvention().apiToImpl(apiType);
		inst = cl.newInstance();
		Long identifier = getNextIdentifier() ;
		ReflectionHelper.invokeMethod(inst, getDomainModelConvention().getSetIdentifierMethod(inst), identifier) ;
		identifierTotransactionObjects.put(identifier, inst);
		return inst;
	}


	/* (non-Javadoc)
	 * @see com.wgo.bpot.server.persist.BasicPersistService#getDomainModelConvention()
	 */
	@Override
	public Db4oDomainModelConvention<S> getDomainModelConvention() {
		return (Db4oDomainModelConvention<S>) super.getDomainModelConvention();
	}
	
}
